package de.hwr.sudokuspielehr.controller;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import de.hwr.sudokuspielehr.data.Sudoku;
import de.hwr.sudokuspielehr.exception.UnknownSyntaxException;
import de.hwr.sudokuspielehr.io.Parser;
import de.hwr.sudokuspielehr.io.Reader;
import de.hwr.sudokuspielehr.io.Writer;

/**
 * 
 * @author J. Reischauer
 * 
 */
public class SudokuController {
	/**
	 * String constant representing the separator in between parameters.
	 */
	private static final String PARAM_SEPARATOR = ",";

	/**
	 * String constant representing the separator in between tags and parameters
	 * (key and value).
	 */
	private static final String TAG_SEPARATOR = ":";

	/**
	 * String constant representing the solution tag in the sudoku files.
	 */
	private static final String TAG_SOLUTION = "solution";
	/**
	 * String constant representing the initial tag in the sudoku files.
	 */
	private static final Object TAG_INITIAL = "initial";

	/**
	 * String constant representing the creation info tag.
	 */
	private static final String TAG_CREATIONINFO = "creationinfo";

	/**
	 * String constant representing the difficulty tag.
	 */
	private static final String TAG_DIFFICULTY = "difficulty";

	/**
	 * All known Sudokus are stored in here.
	 */
	private Map<String, Sudoku> knownSudokus;

	/**
	 * String representing the current location to save data to.
	 */
	private String saveLocation;

	/**
	 * Allows to access the data of this data controller.
	 * 
	 * @return
	 */
	public Map<String, Sudoku> getKnownSudokus() {
		return knownSudokus;
	}

	/**
	 * Allows to set the data of this controller.
	 * 
	 * @param pSudokus
	 *            List containing all known Sudokus
	 */
	public void setKnownSudokus(Map<String, Sudoku> pSudokus) {
		knownSudokus = pSudokus;
	}

	/**
	 * Returns the current save location for Sudokus.
	 * 
	 * @return
	 */
	public String getSaveLocation() {
		return saveLocation;
	}

	/**
	 * Allows to set the path for the saving of Sudokus.
	 * 
	 * @param pLocation
	 *            new path
	 */
	public void setSaveLocation(String pLocation) {
		saveLocation = pLocation;
	}

	/**
	 * Loads all known Sudokus to this controller.
	 * 
	 * @throws IOException
	 */
	public void loadKnownSudokus(String pDefaultPath, String pFileExtension) {
		File[] filesInDirectory = new File(pDefaultPath).listFiles();
		if (filesInDirectory != null) {
			Map<String, Sudoku> sudokus = new HashMap<String, Sudoku>();
			for (File currentFile : filesInDirectory) {
				if (currentFile.getAbsolutePath().endsWith(pFileExtension)) {
					Sudoku currentSudoku = null;
					try {
						currentSudoku = loadSudokuFromFile(currentFile);
					} catch (Exception e) {
						System.out.println("Unable to load Sudoku from " + currentFile.getName() + ".");
					}

					if (currentSudoku != null) {
						sudokus.put(currentFile.getName(), currentSudoku);
					}
				}
			}
			setKnownSudokus(sudokus);
		}
	}

	/**
	 * Allows to load a Sudoku from file.
	 * 
	 * @param pFile
	 *            file to load
	 * @return the Sudoku created from the file, null if unable to create from
	 *         given file
	 * @throws IOException
	 * @throws UnknownSyntaxException
	 */
	public Sudoku loadSudokuFromFile(File pFile) throws IOException, UnknownSyntaxException {
		List<String> currentLines = Reader.read(pFile);
		Map<String, String> parsingResult = Parser.parse(currentLines);
		return createSudoku(parsingResult);
	}

	/**
	 * 
	 * @param pSudoku
	 * @param pFile
	 * @throws IOException
	 */
	public void saveSudoku(Sudoku pSudoku, File pFile) throws IOException {
		int checkSum = pSudoku.getSolvedBoard().length + pSudoku.getInitialBoard().length;
		if (checkSum != 81 * 2) {
			throw new RuntimeException("Data is damaged and cannot be saved.");
		}

		String solutionLine = TAG_SOLUTION + TAG_SEPARATOR;
		int[] solutionParams = pSudoku.getSolvedBoard();
		for (int i = 0; i < solutionParams.length; i++) {
			if (i != 0) {
				solutionLine += PARAM_SEPARATOR;
			}
			solutionLine += String.valueOf(solutionParams[i]);
		}

		String initialLine = TAG_INITIAL + TAG_SEPARATOR;
		int[] initialParams = pSudoku.getInitialBoard();
		for (int i = 0; i < solutionParams.length; i++) {
			if (i != 0) {
				initialLine += PARAM_SEPARATOR;
			}
			initialLine += String.valueOf(initialParams[i]);
		}

		String creationInfoLine = TAG_CREATIONINFO + TAG_SEPARATOR;
		creationInfoLine += pSudoku.getCreationInfo();

		String difficultyLine = TAG_DIFFICULTY + TAG_SEPARATOR;
		difficultyLine += pSudoku.getDifficulty();

		List<String> lines = new ArrayList<String>();
		lines.add(solutionLine);
		lines.add(initialLine);
		lines.add(creationInfoLine);
		lines.add(difficultyLine);

		Writer.write(pFile, lines);
	}

	/**
	 * Creates a Sudoku from given Map of parameters.
	 * 
	 * @param pMap
	 *            the given Map of parameters
	 * @return Sudoku
	 * @throws UnknownSyntaxException
	 *             if given file does not contain required parameters
	 */
	private Sudoku createSudoku(Map<String, String> pMap) throws UnknownSyntaxException {
		Sudoku sudoku = new Sudoku();
		int i = 0;
		for (Entry<String, String> e : pMap.entrySet()) {
			if (e.getKey().equals(TAG_SOLUTION)) {
				String[] params = e.getValue().split(PARAM_SEPARATOR);
				sudoku.setSolvedBoard(createBoardFromParams(params));
				i++;
			} else if (e.getKey().equals(TAG_INITIAL)) {
				String[] params = e.getValue().split(PARAM_SEPARATOR);
				sudoku.setInitialBoard(createBoardFromParams(params));
				sudoku.setBoard(createBoardFromParams(params));
				i++;
			} else if (e.getKey().equals(TAG_CREATIONINFO)) {
				String[] param = e.getValue().split(PARAM_SEPARATOR);
				sudoku.setCreationInfo(param[0]);
				i++;
			} else if (e.getKey().equals(TAG_DIFFICULTY)) {
				String[] param = e.getValue().split(PARAM_SEPARATOR);
				sudoku.setDifficulty(param[0]);
				i++;
			}
		}
		if (i != 4) {
			throw new UnknownSyntaxException("Syntax is messed up!");
		}
		return sudoku;
	}

	/**
	 * 
	 * @param pParams
	 * @return
	 * @throws UnknownSyntaxException
	 */
	private int[] createBoardFromParams(String[] pParams) throws UnknownSyntaxException {
		if (pParams.length != 81) {
			throw new UnknownSyntaxException("Invalid number of arguments!");
		}

		int[] result = new int[81];
		for (int i = 0; i < 81; i++) {
			result[i] = Integer.parseInt(pParams[i]);
		}
		return result;
	}
}
